﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using System.IO;
using System.Globalization;
using SharePoint.Contrib.Extensions;
using System.Reflection;

namespace SharePoint.Contrib
{
    #region [LibraryBuilderExtensions]
    public static class LibraryBuilderExtensions
    {
        /// <summary>
        /// Sets the name (internal name) of the library.
        /// </summary>
        public static LibraryBuilder Name(this LibraryBuilder instance, string name)
        {
            instance.Name = name;
            return instance;
        }

        /// <summary>
        /// Sets the title of the library.
        /// </summary>
        public static LibraryBuilder Title(this LibraryBuilder instance, string title)
        {
            instance.Title = title;
            return instance;
        }

        /// <summary>
        /// Sets the description of the library.
        /// </summary>
        public static LibraryBuilder Description(this LibraryBuilder instance, string description)
        {
            instance.Description = description;
            return instance;
        }

        /// <summary>
        /// Sets a value indicating if the library supports versioning.
        /// </summary>
        public static LibraryBuilder SupportsVersioning(this LibraryBuilder instance, bool enable)
        {
            instance.SupportsVersioning = enable;
            return instance;
        }

        /// <summary>
        /// Sets a value indicating if the library can be crawled.
        /// </summary>
        public static LibraryBuilder CanBeCrawled(this LibraryBuilder instance, bool enable)
        {
            instance.CanBeCrawled = enable;
            return instance;
        }

        /// <summary>
        /// Sets the custom template of the library. Remember, this overrides the TemplateType if set.
        /// </summary>
        public static LibraryBuilder Template(this LibraryBuilder instance, string template)
        {
            instance.Template = template;
            return instance;
        }

        /// <summary>
        /// Sets the template type of the library.
        /// </summary>
        public static LibraryBuilder Template(this LibraryBuilder instance, SPListTemplateType type)
        {
            instance.TemplateType = type;
            return instance;
        }

        /// <summary>
        /// Adds one or more content types that the libary will support.
        /// </summary>
        public static LibraryBuilder ContentTypeSupportFor(this LibraryBuilder instance, params SPContentTypeId[] ids)
        {
            instance.SupportedContentTypes.AddRange(ids);
            return instance;
        }
    } 
    #endregion
    
    /// <summary>
    /// Utility class that helps out with creating instances of SPList or SPDocumentLibrary.
    /// </summary>
    public class LibraryBuilder
    {
        #region [Members]
        private SPList list = null;
        private StringWriter logger; 
        #endregion

        #region [Properties]
        /// <summary>
        /// The SPWeb object on which the library will be created. This property is required.
        /// </summary>
        public SPWeb Site { get; set; }
        /// <summary>
        /// The display name of the library.
        /// </summary>
        public string Title { get; set; }
        /// <summary>
        /// The description of the library.
        /// </summary>
        public string Description { get; set; }
        /// <summary>
        /// The name (internal name) of the library. This property is required.
        /// </summary>
        public string Name { get; set; }
        /// <summary>
        /// Is versioning supported on the library?
        /// </summary>
        public bool SupportsVersioning { get; set; }
        /// <summary>
        /// Can the library be crawled?
        /// </summary>
        public bool CanBeCrawled { get; set; }
        /// <summary>
        /// The name (internal name) of the custom template. Settings this value overrides setting the TemplateType.
        /// </summary>
        public string Template { get; set; }
        /// <summary>
        /// The template type to use. Default is Generic List.
        /// </summary>
        public SPListTemplateType TemplateType { get; set; }
        /// <summary>
        /// Add content types that the library shall support.
        /// </summary>
        public List<SPContentTypeId> SupportedContentTypes { get; private set; } 
        #endregion

        #region [Events]
        /// <summary>
        /// Through this event, logging information is sent.
        /// </summary>
        public event EventHandler<EventArgs<string>> Logging = delegate { }; 
        #endregion

        #region [Methods]
        public LibraryBuilder()
        {
            this.logger = new StringWriter();
            this.TemplateType = SPListTemplateType.GenericList;
            this.SupportedContentTypes = new List<SPContentTypeId>();
        }

        public LibraryBuilder(SPWeb site, string name)
            : this()
        {
            this.Name = name;
            this.Site = site;
        }

        /// <summary>
        /// Creates an instance of the LibraryBuilder class. Remember to set the Name and SIte properties before calling Cast.
        /// </summary>
        public static LibraryBuilder Create()
        {
            return new LibraryBuilder();
        }

        /// <summary>
        /// Creates an instance of the LibraryBuilder class using the SPWeb and the name.
        /// </summary>
        public static LibraryBuilder Create(SPWeb site, string name)
        {
            return new LibraryBuilder(site, name);
        }

        /// <summary>
        /// Calling this method will force the creation of the SPList or SPDocumentLibrary
        /// </summary>
        public LibraryType Cast<LibraryType>() where LibraryType : SPList
        {
            Validate();

            try
            {
                ApplyChanges();
            }
            finally
            {
                logger.Flush();
                Logging(this, new EventArgs<string>(logger.ToString()));
                logger.Close();
            }

            if (list == null)
            {
                throw new Exception("An error occured while accessing the SPList/SPDocumentLibrary. Review the logging information to find out what caused the problem.");
            }

            LibraryType library = list as LibraryType;

            if (library == null)
            {
                throw new Exception(string.Format("Failed to cast SPList to '{0}'", typeof(LibraryType).FullName));
            }

            return library;
        }

        private bool UseContentTypes
        {
            get
            {
                return SupportedContentTypes.Count >= 1;
            }
        }

        private bool UseCustomTemplate
        {
            get
            {
                return string.IsNullOrEmpty(this.Template) == false;
            }
        }

        private void Validate()
        {
            if (string.IsNullOrEmpty(this.Name))
            {
                throw new Exception("A name must be specified for the list.");
            }
            if (Site == null)
            {
                throw new Exception("SPWeb instance must be specified");
            }
        }

        private void ApplyChanges()
        {
            if (Site.TryFindListByName(this.Name, out list) == false)
            {
                logger.WriteLine("The list '{0}' was not found, trying to create it.", this.Name);

                Site.RunWithUnsafeUpdates(SPSite =>
                    {
                        Guid id = Guid.Empty;

                        if (UseCustomTemplate)
                        {
                            SPListTemplate template = SPSite.FindTemplateByInternalName(this.Template);
                            if (template == null)
                            {
                                ReportError(string.Format(CultureInfo.CurrentCulture,
                                    "Failed to find SPListTemplate '{0}' on site '{1}'.",
                                    this.Template, Site.Url));

                            }
                            try
                            {
                                logger.WriteLine("Trying to create list '{0}' with template '{1}'", this.Name, template.InternalName);
                                id = SPSite.Lists.Add(this.Name, this.Description, template);
                            }
                            catch (Exception e)
                            {
                                ReportError("Failed to create list", e);
                            }
                        }
                        else
                        {
                            try
                            {
                                logger.WriteLine("Trying to create list '{0}' with template '{1}'", this.Name, this.TemplateType);
                                id = SPSite.Lists.Add(this.Name, this.Description, this.TemplateType);
                            }
                            catch (Exception e)
                            {
                                ReportError("Failed to create list", e);
                            }

                        }
                        logger.WriteLine("List '{0}' created", this.Name);
                        list = SPSite.Lists.GetList(id, false);
                    });
            }
            else
            {
                logger.WriteLine("The list '{0}' has already been created.", this.Name);
            }

            UpdateListProperties();
            UpdateListContentTypes();

            try
            {
                list.Update();
            }
            catch (Exception e)
            {
                ReportError("Failed to update the list. [ SPList.Update() ]", e);
            }
        }

        private void UpdateListContentTypes()
        {
            if (UseContentTypes)
            {
                list.ContentTypesEnabled = true;

                SupportedContentTypes.ForEach(id =>
                {
                    try
                    {
                        list.AddContentType(id);
                        logger.WriteLine("Content type with id '{0}' added successfully (or it might already exist)", id);
                    }
                    catch (Exception e)
                    {
                        ReportError(string.Format(CultureInfo.CurrentCulture,
                            "An error occured when adding content type with id '{0}'",
                            id), e);
                    }
                });

            }
        }

        private void UpdateListProperties()
        {
            logger.WriteLine("--- This is a summary of the list properties ---");
            logger.WriteLine("Description: {0}", Description);
            logger.WriteLine("CanBeCrawled: {0}", CanBeCrawled);
            logger.WriteLine("SupportsVersioning: {0}", SupportsVersioning);
            logger.WriteLine("Title: {0}", Title);
            logger.WriteLine("Name: {0}", Name);
            logger.WriteLine("--- END ---");

            list.Description = Description ?? string.Format("This is the '{0}' list, created by the LibraryBuilder class.", Name);
            list.NoCrawl = !CanBeCrawled;
            list.EnableVersioning = SupportsVersioning;
            list.Title = Title ?? Name;
        }

        private void ReportError(string message)
        {
            ReportError(message, null);
        }
        private void ReportError(string message, Exception e)
        {
            logger.WriteLine("# {0}{1}# {2}", message, Environment.NewLine, e);
        } 
        #endregion
    }

}
